2.3.4 Types

Section §2.3.2 described nine kinds of values. Each kind of value is captured by a descriptor called a type. Myron types are real, complex (formally: complex-I), radial, column vector, row vector, set, tuple, matrix and string. Formally, a type specifies the internal representation of values and the transformations that can be applied to them. Types are used in parameter inference, definition and equation balancing, function and variable binding and workspace display.

In addition to the nine types, a tenth type – unknown – is produced when the type system fails to identify a type. The unknown type will only appear in an error message. Moreover, reals are subtyped internally as boolean, integer, decimal and complex-j to simplify the task of applying operators. The real subtypes are completely transparent to the user.

Myron associates a type with each variable, parameter, function and operator. The type of a variable or function is given by its type decoration which can be provided explicitly or inferred from the type of its elaboration. The type of an operator (or more formally, the type of the result produce by applying an operator to its operands) depends on the nature of the operator and the types of its operand(s). Some operators are defined to produce a particular result type regardless of operand type; others are defined to produce a type that depends on the operand types.

The order of the type list is important because it is used to determine the type of an operator when its operands have mixed types. The real type is said to be at the “low” end of the list and the string type is at the “high” end. This allows us to talk about the relative order of types and say that one type is lower or higher than another.

Values can be transformed to other types. When a type transformation is applied explicitly, it is called a cast. When it is applied implicitly, it is called a coercion.

The general rule for binary operators with mixed-type operands is to coerce the operand with lower type to that of the higher type. For example, the complex operand in (1, 2)ʋ+(3, 4)ⅈ is transformed to a row vector before the addition operation is applied.

The cast operator is any of the formal or keyboard type notations used as a postfix operator. For example, if addition of a row vector and complex value is required to produce a complex result, the row vector must be transformed using the ⅈ postfix cast operator: (1, 2)ʋⅈ+(3, 4)ⅈ.

When type transformation takes a lower type to a higher type, it is called a widening. In most cases, widenings do not cause loss of component or element information. A type transformation in the other direction is called a narrowing. Information can be lost when narrowing. For example, vectors can contain any number of scalars, but complex values can contain only two; matrices contain rows and columns but a row vector contains just rows and a column vector contains just columns; a tuple contains any type of value but a row-vector contains just scalars.

When narrowing would cause loss of information, the transformation fails. This is apparent in the workspace when simplification of a cast expression makes no change. Narrowings can be forced by casting first to tuple. For example, casting a 3-component vector to complex is not permitted, but casting from a 3-element tuple discards the third element. Requiring the intermediate tuple prevents inadvertent errors and requires the user to think about the transformation. Item-loss aside, casting from collection to composite also fails if the collection contains non-scalar elements.

Type transformation involving sets can also cause loss of information, even when widening. For example, vectors can contain more than one component with the same value. Widening to a set causes duplicate elements to be discarded. Because of this, composites cannot be cast to sets; rather an intermediate widening to tuple is required, followed by a narrowing from tuple to set. That is, (1,1)v s is illegal but (1, 1)ʋʈʂ is permitted. Sets can be narrowed to vectors provided the elements are all scalar, but having no order there is no intrinsic correspondence between set element and vector component.

Casting to a string creates a string with the parsable representation of the operand. Casting from a string to any other type parses the string to produce the expression represented by the string, then casts that expression to the desired type.

The type of a unary operator is generally the type of its operand except where the operator is defined to always result in a particular type. When a unary operator is distributed across components of composite types and elements of collection types, the resulting expression still has the same type. For example, the type of -(1, 2)ʋ is vector. After simplification to (-1, -2)ʋ, the type remains the same. On the other hand, the type of #(1, 2)ʋ is real. After simplification, the value is 2, still a real.

Although the higher-type rule for binary operators is attractive in its simplicity, there are several exceptions. Mixing tuples and sets with other operands uses elaborate coercion rules, described in §6.2.1. Other exceptions include the containment operator, which always returns a real result. Other operators that return a real from non-real operands are the set-test operators: subset, proper subset, superset, proper superset.

Set operations intersection, union and difference are also exceptions. For set and tuple operands, the higher-type rule applies. (Under set operations, tuples implement multisets.) However, if one of the operands is neither set nor tuple, the latter is promoted to a singleton of the former.

Multiplication is another exception. A special multiplication operator is used when the operands are composite or matrix. For radial and vector operands, multiplication produces the dot product, returning a scalar. The dot product is computed similarly to the matrix product of a 1-row matrix with a 1-column matrix except the result of the latter is a 1-row 1-column matrix instead of a scalar.

Although all of this seems unnecessarily complicated, there are several reasons for having a type system in Myron. Foremost, checking types when an expression is parsed reduces the number of situations in which types will be incompatible when simplified or transformed. In addition, the type system reduces the potential for mathematically meaningless expressions. For example, the equation .[x+1={1, 2}+1] (shown here as a protected expression to get it past the parser) has a real on the left and a set on the right. No definition of equivalence can produce a mathematically valid equation of this sort. Note, however, that when presented with an expression with mixed types such as this, the parser provides an explicit cast to the higher type.

With the type system comes the expectation that no transformation will change the type of an expression. This is required to maintain the balance of equations and definitions with respect to type. To this end, transform expressions have the requirement that the pattern and replacement have the same type. This is already the case for substitution rules, which begin life as equations and are type checked by the parser.